Tweak metadata to publish Cargo on crates.io
authorAlex Crichton <alex@alexcrichton.com>
Fri, 24 Jul 2015 21:37:03 +0000 (14:37 -0700)
committerAlex Crichton <alex@alexcrichton.com>
Fri, 24 Jul 2015 22:07:01 +0000 (15:07 -0700)
This commit tweaks some metadata here and there to publish Cargo on crates.io.

* License fields are added to Cargo.tomls
* `registry` was renamed to `crates-io`
* API docs for the `cargo` crate are now generated via `make doc`

Cargo.lock
Cargo.toml
Makefile.in
src/cargo/lib.rs
src/crates-io/Cargo.toml [new file with mode: 0644]
src/crates-io/lib.rs [new file with mode: 0644]
src/registry/Cargo.toml [deleted file]
src/registry/lib.rs [deleted file]

index efdea829484810725f143ba80aeb55ebadccdcea..63a086d32b050bec08511b5b39a6d8c2399e3158 100644 (file)
@@ -4,6 +4,7 @@ version = "0.4.0"
 dependencies = [
  "advapi32-sys 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "bufstream 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "crates-io 0.1.0",
  "curl 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)",
  "docopt 0.6.67 (registry+https://github.com/rust-lang/crates.io-index)",
  "env_logger 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -12,14 +13,13 @@ dependencies = [
  "git2 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)",
  "git2-curl 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)",
  "glob 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)",
- "hamcrest 0.1.0 (git+https://github.com/carllerche/hamcrest-rust.git)",
+ "hamcrest 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "kernel32-sys 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "libc 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
  "libgit2-sys 0.2.20 (registry+https://github.com/rust-lang/crates.io-index)",
  "log 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "num_cpus 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)",
  "regex 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)",
- "registry 0.1.0",
  "rustc-serialize 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)",
  "semver 0.1.19 (registry+https://github.com/rust-lang/crates.io-index)",
  "tar 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -59,6 +59,14 @@ name = "bufstream"
 version = "0.1.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
+[[package]]
+name = "crates-io"
+version = "0.1.0"
+dependencies = [
+ "curl 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rustc-serialize 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
 [[package]]
 name = "curl"
 version = "0.2.10"
@@ -155,7 +163,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 [[package]]
 name = "hamcrest"
 version = "0.1.0"
-source = "git+https://github.com/carllerche/hamcrest-rust.git#b61fef3e6d47114f86e5e16e26f7013e9fa8071e"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "num 0.1.25 (registry+https://github.com/rust-lang/crates.io-index)",
+]
 
 [[package]]
 name = "kernel32-sys"
@@ -243,6 +254,15 @@ dependencies = [
  "libc 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
+[[package]]
+name = "num"
+version = "0.1.25"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "rand 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rustc-serialize 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
 [[package]]
 name = "num_cpus"
 version = "0.2.6"
@@ -298,14 +318,6 @@ name = "regex-syntax"
 version = "0.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
-[[package]]
-name = "registry"
-version = "0.1.0"
-dependencies = [
- "curl 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)",
- "rustc-serialize 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)",
-]
-
 [[package]]
 name = "rustc-serialize"
 version = "0.3.14"
index 61f16daca9a2924878e900e0ec08149f30a88fc2..70635a2fe1e95222edb6c2af545b1484bd01dce5 100644 (file)
@@ -4,6 +4,13 @@ version = "0.4.0"
 authors = ["Yehuda Katz <wycats@gmail.com>",
            "Carl Lerche <me@carllerche.com>",
            "Alex Crichton <alex@alexcrichton.com>"]
+license = "MIT/Apache-2.0"
+homepage = "https://crates.io"
+repository = "https://github.com/rust-lang/cargo"
+documentation = "http://doc.crates.io"
+description = """
+Cargo, a package manager for Rust.
+"""
 
 [lib]
 name = "cargo"
@@ -25,7 +32,7 @@ libgit2-sys = "0.2"
 log = "0.3"
 num_cpus = "0.2"
 regex = "0.1"
-registry = { path = "src/registry" }
+crates-io = { path = "src/crates-io", version = "0.1" }
 rustc-serialize = "0.3"
 semver = "0.1"
 tar = { version = "0.2", features = ["nightly"] }
@@ -38,7 +45,7 @@ winapi = "0.2"
 
 [dev-dependencies]
 tempdir = "0.3"
-hamcrest = { git = "https://github.com/carllerche/hamcrest-rust.git" }
+hamcrest = "0.1"
 bufstream = "0.1"
 filetime = "0.1"
 
index a8d189ba1caed1dabf9a3133af3e951032da8094..d7322076e29df890f73e0e2ee70b0af1f7e98691 100644 (file)
@@ -133,7 +133,11 @@ ASSETS := CNAME images/noise.png images/forkme.png images/Cargo-Logo-Small.png \
        images/search.png
 
 doc: $(foreach doc,$(DOCS),target/doc/$(doc).html) \
-       $(foreach asset,$(ASSETS),target/doc/$(asset))
+       $(foreach asset,$(ASSETS),target/doc/$(asset)) \
+       target/doc/cargo/index.html
+
+target/doc/cargo/index.html:
+       $(CARGO) doc --no-deps
 
 $(DOC_DIR)/%.html: src/doc/%.md src/doc/header.html src/doc/footer.html
        @mkdir -p $(@D)
index 33c83add0ba5fd7618d91aac582bd66d9bd6ab28..1accb5e2c1bd3f578c6eb4e5522c1600565e4032 100644 (file)
@@ -3,6 +3,7 @@
 
 #[cfg(test)] extern crate hamcrest;
 #[macro_use] extern crate log;
+extern crate crates_io as registry;
 extern crate curl;
 extern crate docopt;
 extern crate filetime;
@@ -13,7 +14,6 @@ extern crate libc;
 extern crate libgit2_sys;
 extern crate num_cpus;
 extern crate regex;
-extern crate registry;
 extern crate rustc_serialize;
 extern crate semver;
 extern crate tar;
diff --git a/src/crates-io/Cargo.toml b/src/crates-io/Cargo.toml
new file mode 100644 (file)
index 0000000..70068ec
--- /dev/null
@@ -0,0 +1,17 @@
+[package]
+name = "crates-io"
+version = "0.1.0"
+authors = ["Alex Crichton <alex@alexcrichton.com>"]
+license = "MIT/Apache-2.0"
+repository = "https://github.com/rust-lang/cargo"
+description = """
+Helpers for interacting with crates.io
+"""
+
+[lib]
+name = "crates_io"
+path = "lib.rs"
+
+[dependencies]
+curl = "0.2"
+rustc-serialize = "0.3"
diff --git a/src/crates-io/lib.rs b/src/crates-io/lib.rs
new file mode 100644 (file)
index 0000000..11787a2
--- /dev/null
@@ -0,0 +1,268 @@
+extern crate curl;
+extern crate rustc_serialize;
+
+use std::collections::HashMap;
+use std::fmt;
+use std::fs::{self, File};
+use std::io::prelude::*;
+use std::io::{self, Cursor};
+use std::path::Path;
+use std::result;
+
+use curl::http;
+use curl::http::handle::Method::{Put, Get, Delete};
+use curl::http::handle::{Method, Request};
+use rustc_serialize::json;
+
+pub struct Registry {
+    host: String,
+    token: Option<String>,
+    handle: http::Handle,
+}
+
+pub type Result<T> = result::Result<T, Error>;
+
+#[derive(PartialEq, Clone, Copy)]
+pub enum Auth {
+    Authorized,
+    Unauthorized
+}
+
+pub enum Error {
+    Curl(curl::ErrCode),
+    NotOkResponse(http::Response),
+    NonUtf8Body,
+    Api(Vec<String>),
+    Unauthorized,
+    TokenMissing,
+    Io(io::Error),
+}
+
+#[derive(RustcDecodable)]
+pub struct Crate {
+    pub name: String,
+    pub description: Option<String>,
+    pub max_version: String
+}
+
+#[derive(RustcEncodable)]
+pub struct NewCrate {
+    pub name: String,
+    pub vers: String,
+    pub deps: Vec<NewCrateDependency>,
+    pub features: HashMap<String, Vec<String>>,
+    pub authors: Vec<String>,
+    pub description: Option<String>,
+    pub documentation: Option<String>,
+    pub homepage: Option<String>,
+    pub readme: Option<String>,
+    pub keywords: Vec<String>,
+    pub license: Option<String>,
+    pub license_file: Option<String>,
+    pub repository: Option<String>,
+}
+
+#[derive(RustcEncodable)]
+pub struct NewCrateDependency {
+    pub optional: bool,
+    pub default_features: bool,
+    pub name: String,
+    pub features: Vec<String>,
+    pub version_req: String,
+    pub target: Option<String>,
+    pub kind: String,
+}
+
+#[derive(RustcDecodable)]
+pub struct User {
+    pub id: u32,
+    pub login: String,
+    pub avatar: String,
+    pub email: Option<String>,
+    pub name: Option<String>,
+}
+
+#[derive(RustcDecodable)] struct R { ok: bool }
+#[derive(RustcDecodable)] struct ApiErrorList { errors: Vec<ApiError> }
+#[derive(RustcDecodable)] struct ApiError { detail: String }
+#[derive(RustcEncodable)] struct OwnersReq<'a> { users: &'a [&'a str] }
+#[derive(RustcDecodable)] struct Users { users: Vec<User> }
+#[derive(RustcDecodable)] struct Crates { crates: Vec<Crate> }
+
+impl Registry {
+    pub fn new(host: String, token: Option<String>) -> Registry {
+        Registry::new_handle(host, token, http::Handle::new())
+    }
+
+    pub fn new_handle(host: String, token: Option<String>,
+                      handle: http::Handle) -> Registry {
+        Registry {
+            host: host,
+            token: token,
+            handle: handle,
+        }
+    }
+
+    pub fn add_owners(&mut self, krate: &str, owners: &[&str]) -> Result<()> {
+        let body = json::encode(&OwnersReq { users: owners }).unwrap();
+        let body = try!(self.put(format!("/crates/{}/owners", krate),
+                                 body.as_bytes()));
+        assert!(json::decode::<R>(&body).unwrap().ok);
+        Ok(())
+    }
+
+    pub fn remove_owners(&mut self, krate: &str, owners: &[&str]) -> Result<()> {
+        let body = json::encode(&OwnersReq { users: owners }).unwrap();
+        let body = try!(self.delete(format!("/crates/{}/owners", krate),
+                                    Some(body.as_bytes())));
+        assert!(json::decode::<R>(&body).unwrap().ok);
+        Ok(())
+    }
+
+    pub fn list_owners(&mut self, krate: &str) -> Result<Vec<User>> {
+        let body = try!(self.get(format!("/crates/{}/owners", krate)));
+        Ok(json::decode::<Users>(&body).unwrap().users)
+    }
+
+    pub fn publish(&mut self, krate: &NewCrate, tarball: &Path) -> Result<()> {
+        let json = json::encode(krate).unwrap();
+        // Prepare the body. The format of the upload request is:
+        //
+        //      <le u32 of json>
+        //      <json request> (metadata for the package)
+        //      <le u32 of tarball>
+        //      <source tarball>
+        let stat = try!(fs::metadata(tarball).map_err(Error::Io));
+        let header = {
+            let mut w = Vec::new();
+            w.extend([
+                (json.len() >>  0) as u8,
+                (json.len() >>  8) as u8,
+                (json.len() >> 16) as u8,
+                (json.len() >> 24) as u8,
+            ].iter().map(|x| *x));
+            w.extend(json.as_bytes().iter().map(|x| *x));
+            w.extend([
+                (stat.len() >>  0) as u8,
+                (stat.len() >>  8) as u8,
+                (stat.len() >> 16) as u8,
+                (stat.len() >> 24) as u8,
+            ].iter().map(|x| *x));
+            w
+        };
+        let tarball = try!(File::open(tarball).map_err(Error::Io));
+        let size = stat.len() as usize + header.len();
+        let mut body = Cursor::new(header).chain(tarball);
+
+        let url = format!("{}/api/v1/crates/new", self.host);
+
+        let token = match self.token.as_ref() {
+            Some(s) => s,
+            None => return Err(Error::TokenMissing),
+        };
+        let request = self.handle.put(url, &mut body)
+            .content_length(size)
+            .header("Accept", "application/json")
+            .header("Authorization", &token);
+        let response = handle(request.exec());
+        let _body = try!(response);
+        Ok(())
+    }
+
+    pub fn search(&mut self, query: &str) -> Result<Vec<Crate>> {
+        let body = try!(self.req(format!("/crates?q={}", query), None, Get,
+                                 Auth::Unauthorized));
+
+        Ok(json::decode::<Crates>(&body).unwrap().crates)
+    }
+
+    pub fn yank(&mut self, krate: &str, version: &str) -> Result<()> {
+        let body = try!(self.delete(format!("/crates/{}/{}/yank", krate, version),
+                                    None));
+        assert!(json::decode::<R>(&body).unwrap().ok);
+        Ok(())
+    }
+
+    pub fn unyank(&mut self, krate: &str, version: &str) -> Result<()> {
+        let body = try!(self.put(format!("/crates/{}/{}/unyank", krate, version),
+                                 &[]));
+        assert!(json::decode::<R>(&body).unwrap().ok);
+        Ok(())
+    }
+
+    fn put(&mut self, path: String, b: &[u8]) -> Result<String> {
+        self.req(path, Some(b), Put, Auth::Authorized)
+    }
+
+    fn get(&mut self, path: String) -> Result<String> {
+        self.req(path, None, Get, Auth::Authorized)
+    }
+
+    fn delete(&mut self, path: String, b: Option<&[u8]>) -> Result<String> {
+        self.req(path, b, Delete, Auth::Authorized)
+    }
+
+    fn req(&mut self, path: String, body: Option<&[u8]>,
+           method: Method, authorized: Auth) -> Result<String> {
+        let mut req = Request::new(&mut self.handle, method)
+                              .uri(format!("{}/api/v1{}", self.host, path))
+                              .header("Accept", "application/json")
+                              .content_type("application/json");
+
+        if authorized == Auth::Authorized {
+            let token = match self.token.as_ref() {
+                Some(s) => s,
+                None => return Err(Error::TokenMissing),
+            };
+            req = req.header("Authorization", &token);
+        }
+        match body {
+            Some(b) => req = req.body(b),
+            None => {}
+        }
+        handle(req.exec())
+    }
+}
+
+fn handle(response: result::Result<http::Response, curl::ErrCode>)
+          -> Result<String> {
+    let response = try!(response.map_err(Error::Curl));
+    match response.get_code() {
+        0 => {} // file upload url sometimes
+        200 => {}
+        403 => return Err(Error::Unauthorized),
+        _ => return Err(Error::NotOkResponse(response))
+    }
+
+    let body = match String::from_utf8(response.move_body()) {
+        Ok(body) => body,
+        Err(..) => return Err(Error::NonUtf8Body),
+    };
+    match json::decode::<ApiErrorList>(&body) {
+        Ok(errors) => {
+            return Err(Error::Api(errors.errors.into_iter().map(|s| s.detail)
+                                        .collect()))
+        }
+        Err(..) => {}
+    }
+    Ok(body)
+}
+
+impl fmt::Display for Error {
+    #[allow(deprecated)] // connect => join in 1.3
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        match *self {
+            Error::NonUtf8Body => write!(f, "response body was not utf-8"),
+            Error::Curl(ref err) => write!(f, "http error: {}", err),
+            Error::NotOkResponse(ref resp) => {
+                write!(f, "failed to get a 200 OK response: {}", resp)
+            }
+            Error::Api(ref errs) => {
+                write!(f, "api errors: {}", errs.connect(", "))
+            }
+            Error::Unauthorized => write!(f, "unauthorized API access"),
+            Error::TokenMissing => write!(f, "no upload token found, please run `cargo login`"),
+            Error::Io(ref e) => write!(f, "io error: {}", e),
+        }
+    }
+}
diff --git a/src/registry/Cargo.toml b/src/registry/Cargo.toml
deleted file mode 100644 (file)
index 622f99e..0000000
+++ /dev/null
@@ -1,12 +0,0 @@
-[package]
-name = "registry"
-version = "0.1.0"
-authors = ["Alex Crichton <alex@alexcrichton.com>"]
-
-[lib]
-name = "registry"
-path = "lib.rs"
-
-[dependencies]
-curl = "0.2"
-rustc-serialize = "0.3"
diff --git a/src/registry/lib.rs b/src/registry/lib.rs
deleted file mode 100644 (file)
index 11787a2..0000000
+++ /dev/null
@@ -1,268 +0,0 @@
-extern crate curl;
-extern crate rustc_serialize;
-
-use std::collections::HashMap;
-use std::fmt;
-use std::fs::{self, File};
-use std::io::prelude::*;
-use std::io::{self, Cursor};
-use std::path::Path;
-use std::result;
-
-use curl::http;
-use curl::http::handle::Method::{Put, Get, Delete};
-use curl::http::handle::{Method, Request};
-use rustc_serialize::json;
-
-pub struct Registry {
-    host: String,
-    token: Option<String>,
-    handle: http::Handle,
-}
-
-pub type Result<T> = result::Result<T, Error>;
-
-#[derive(PartialEq, Clone, Copy)]
-pub enum Auth {
-    Authorized,
-    Unauthorized
-}
-
-pub enum Error {
-    Curl(curl::ErrCode),
-    NotOkResponse(http::Response),
-    NonUtf8Body,
-    Api(Vec<String>),
-    Unauthorized,
-    TokenMissing,
-    Io(io::Error),
-}
-
-#[derive(RustcDecodable)]
-pub struct Crate {
-    pub name: String,
-    pub description: Option<String>,
-    pub max_version: String
-}
-
-#[derive(RustcEncodable)]
-pub struct NewCrate {
-    pub name: String,
-    pub vers: String,
-    pub deps: Vec<NewCrateDependency>,
-    pub features: HashMap<String, Vec<String>>,
-    pub authors: Vec<String>,
-    pub description: Option<String>,
-    pub documentation: Option<String>,
-    pub homepage: Option<String>,
-    pub readme: Option<String>,
-    pub keywords: Vec<String>,
-    pub license: Option<String>,
-    pub license_file: Option<String>,
-    pub repository: Option<String>,
-}
-
-#[derive(RustcEncodable)]
-pub struct NewCrateDependency {
-    pub optional: bool,
-    pub default_features: bool,
-    pub name: String,
-    pub features: Vec<String>,
-    pub version_req: String,
-    pub target: Option<String>,
-    pub kind: String,
-}
-
-#[derive(RustcDecodable)]
-pub struct User {
-    pub id: u32,
-    pub login: String,
-    pub avatar: String,
-    pub email: Option<String>,
-    pub name: Option<String>,
-}
-
-#[derive(RustcDecodable)] struct R { ok: bool }
-#[derive(RustcDecodable)] struct ApiErrorList { errors: Vec<ApiError> }
-#[derive(RustcDecodable)] struct ApiError { detail: String }
-#[derive(RustcEncodable)] struct OwnersReq<'a> { users: &'a [&'a str] }
-#[derive(RustcDecodable)] struct Users { users: Vec<User> }
-#[derive(RustcDecodable)] struct Crates { crates: Vec<Crate> }
-
-impl Registry {
-    pub fn new(host: String, token: Option<String>) -> Registry {
-        Registry::new_handle(host, token, http::Handle::new())
-    }
-
-    pub fn new_handle(host: String, token: Option<String>,
-                      handle: http::Handle) -> Registry {
-        Registry {
-            host: host,
-            token: token,
-            handle: handle,
-        }
-    }
-
-    pub fn add_owners(&mut self, krate: &str, owners: &[&str]) -> Result<()> {
-        let body = json::encode(&OwnersReq { users: owners }).unwrap();
-        let body = try!(self.put(format!("/crates/{}/owners", krate),
-                                 body.as_bytes()));
-        assert!(json::decode::<R>(&body).unwrap().ok);
-        Ok(())
-    }
-
-    pub fn remove_owners(&mut self, krate: &str, owners: &[&str]) -> Result<()> {
-        let body = json::encode(&OwnersReq { users: owners }).unwrap();
-        let body = try!(self.delete(format!("/crates/{}/owners", krate),
-                                    Some(body.as_bytes())));
-        assert!(json::decode::<R>(&body).unwrap().ok);
-        Ok(())
-    }
-
-    pub fn list_owners(&mut self, krate: &str) -> Result<Vec<User>> {
-        let body = try!(self.get(format!("/crates/{}/owners", krate)));
-        Ok(json::decode::<Users>(&body).unwrap().users)
-    }
-
-    pub fn publish(&mut self, krate: &NewCrate, tarball: &Path) -> Result<()> {
-        let json = json::encode(krate).unwrap();
-        // Prepare the body. The format of the upload request is:
-        //
-        //      <le u32 of json>
-        //      <json request> (metadata for the package)
-        //      <le u32 of tarball>
-        //      <source tarball>
-        let stat = try!(fs::metadata(tarball).map_err(Error::Io));
-        let header = {
-            let mut w = Vec::new();
-            w.extend([
-                (json.len() >>  0) as u8,
-                (json.len() >>  8) as u8,
-                (json.len() >> 16) as u8,
-                (json.len() >> 24) as u8,
-            ].iter().map(|x| *x));
-            w.extend(json.as_bytes().iter().map(|x| *x));
-            w.extend([
-                (stat.len() >>  0) as u8,
-                (stat.len() >>  8) as u8,
-                (stat.len() >> 16) as u8,
-                (stat.len() >> 24) as u8,
-            ].iter().map(|x| *x));
-            w
-        };
-        let tarball = try!(File::open(tarball).map_err(Error::Io));
-        let size = stat.len() as usize + header.len();
-        let mut body = Cursor::new(header).chain(tarball);
-
-        let url = format!("{}/api/v1/crates/new", self.host);
-
-        let token = match self.token.as_ref() {
-            Some(s) => s,
-            None => return Err(Error::TokenMissing),
-        };
-        let request = self.handle.put(url, &mut body)
-            .content_length(size)
-            .header("Accept", "application/json")
-            .header("Authorization", &token);
-        let response = handle(request.exec());
-        let _body = try!(response);
-        Ok(())
-    }
-
-    pub fn search(&mut self, query: &str) -> Result<Vec<Crate>> {
-        let body = try!(self.req(format!("/crates?q={}", query), None, Get,
-                                 Auth::Unauthorized));
-
-        Ok(json::decode::<Crates>(&body).unwrap().crates)
-    }
-
-    pub fn yank(&mut self, krate: &str, version: &str) -> Result<()> {
-        let body = try!(self.delete(format!("/crates/{}/{}/yank", krate, version),
-                                    None));
-        assert!(json::decode::<R>(&body).unwrap().ok);
-        Ok(())
-    }
-
-    pub fn unyank(&mut self, krate: &str, version: &str) -> Result<()> {
-        let body = try!(self.put(format!("/crates/{}/{}/unyank", krate, version),
-                                 &[]));
-        assert!(json::decode::<R>(&body).unwrap().ok);
-        Ok(())
-    }
-
-    fn put(&mut self, path: String, b: &[u8]) -> Result<String> {
-        self.req(path, Some(b), Put, Auth::Authorized)
-    }
-
-    fn get(&mut self, path: String) -> Result<String> {
-        self.req(path, None, Get, Auth::Authorized)
-    }
-
-    fn delete(&mut self, path: String, b: Option<&[u8]>) -> Result<String> {
-        self.req(path, b, Delete, Auth::Authorized)
-    }
-
-    fn req(&mut self, path: String, body: Option<&[u8]>,
-           method: Method, authorized: Auth) -> Result<String> {
-        let mut req = Request::new(&mut self.handle, method)
-                              .uri(format!("{}/api/v1{}", self.host, path))
-                              .header("Accept", "application/json")
-                              .content_type("application/json");
-
-        if authorized == Auth::Authorized {
-            let token = match self.token.as_ref() {
-                Some(s) => s,
-                None => return Err(Error::TokenMissing),
-            };
-            req = req.header("Authorization", &token);
-        }
-        match body {
-            Some(b) => req = req.body(b),
-            None => {}
-        }
-        handle(req.exec())
-    }
-}
-
-fn handle(response: result::Result<http::Response, curl::ErrCode>)
-          -> Result<String> {
-    let response = try!(response.map_err(Error::Curl));
-    match response.get_code() {
-        0 => {} // file upload url sometimes
-        200 => {}
-        403 => return Err(Error::Unauthorized),
-        _ => return Err(Error::NotOkResponse(response))
-    }
-
-    let body = match String::from_utf8(response.move_body()) {
-        Ok(body) => body,
-        Err(..) => return Err(Error::NonUtf8Body),
-    };
-    match json::decode::<ApiErrorList>(&body) {
-        Ok(errors) => {
-            return Err(Error::Api(errors.errors.into_iter().map(|s| s.detail)
-                                        .collect()))
-        }
-        Err(..) => {}
-    }
-    Ok(body)
-}
-
-impl fmt::Display for Error {
-    #[allow(deprecated)] // connect => join in 1.3
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        match *self {
-            Error::NonUtf8Body => write!(f, "response body was not utf-8"),
-            Error::Curl(ref err) => write!(f, "http error: {}", err),
-            Error::NotOkResponse(ref resp) => {
-                write!(f, "failed to get a 200 OK response: {}", resp)
-            }
-            Error::Api(ref errs) => {
-                write!(f, "api errors: {}", errs.connect(", "))
-            }
-            Error::Unauthorized => write!(f, "unauthorized API access"),
-            Error::TokenMissing => write!(f, "no upload token found, please run `cargo login`"),
-            Error::Io(ref e) => write!(f, "io error: {}", e),
-        }
-    }
-}